{******************************Version: MPL 1.1*********************************
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Initial Developer : Fajar Khairil
*
* Portions created by the Initial Developer are Copyright (C) 2010 - 2011
* the Initial Developer. All Rights Reserved.
*
********************************************************************************
* This File is Part of HdFramework ___ Library
* Copyright (C) 2010 - 2011 HdFramework Developer Team
*
* Unit : HadeSession
* Description : -
* Author: - Fajar Khairil
*******************************************************************************}
unit hadesession;

{$mode objfpc}{$H+}

interface

uses
  hadeconnectionmanager,
  hadebaseobject,
  hadebasevisitor,
  hadecriteria,
  SysUtils,
  hadeobject,
  hademapping;

type
  { ThadeSession }
  { we use T_Prefix for session interface }
  THadeSession = interface
    ['{61E590B3-B415-4B78-BF24-19CF04718DD0}']
    procedure save(const AObject: THadeVisited);

    { we have 5 read overload procedure }
    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria;
      const APropsOrder: array of string; const AOrderType: THadeOrderType = oASC;
      const limit: integer = 0; const offset: integer = 0);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria;
      const APropsOrder: array of string; const AOrderType: THadeOrderType = oASC);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria;
      const limit: integer; const offset: integer);

    procedure Read(const AObject: THadeVisited; const limit: integer;
      const offset: integer);

    procedure Read(const AObject: THadeVisited);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria);

    //Procedure RetrieveObjectReference();

    procedure StartTransaction;
    procedure Commit;
    procedure Rollback;
  end;

  { ThadeSessionImplementor }
  { TODO 2 -oFajar -cOPF : make thread safe this class }
  ThadeSessionImplementor = class(THadeBaseObject, THadeSession)
  private
    FConnection: THadeConnection;
    IsTransactStart: boolean;
    rolledback: boolean;
    fReturnConnection: boolean;
  protected
    procedure saveObject(const AObject: THadeVisited);
    procedure saveObjectList(const AObject: THadeVisited);
  public
    procedure save(const AObject: THadeVisited);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria;
      const APropsOrder: array of string; const AOrderType: THadeOrderType = oASC;
      const limit: integer = 0; const offset: integer = 0);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria;
      const APropsOrder: array of string; const AOrderType: THadeOrderType = oASC);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria;
      const limit: integer = 0; const offset: integer = 0);

    procedure Read(const AObject: THadeVisited);

    procedure Read(const AObject: THadeVisited; const limit: integer = 0;
      const offset: integer = 0);

    procedure Read(const AObject: THadeVisited; const ACriteria: THadeCriteria);

    procedure StartTransaction;
    procedure Commit;
    procedure Rollback;

    constructor Create(const AConnectionName: string; AReturnConnection: boolean = True);
    destructor Destroy; override;
  end;

  { GHadeSessionFactory }

  GHadeSessionFactory = class
  public
    class function CreateSession(const AConnectionName: string;
      AReturnConnection: boolean = True): THadeSession;
  end;

implementation

uses
  hadeopfmanager,
  hadeexception,
  hadeconst;

class function GHadeSessionFactory.CreateSession(const AConnectionName: string;
  AReturnConnection: boolean): THadeSession;
begin
  Result := ThadeSessionImplementor.Create(AConnectionName, AReturnConnection);
end;

{ ThadeSession }

procedure ThadeSessionImplementor.saveObject(const AObject: THadeVisited);
begin
  case THadeObject(AObject).objectState of
    posCREATE: GHadeOPFManager.VisitorManager.ExecuteVisitor(
        THadeObject(AObject).getCreateVisitor, AObject, FConnection);
    posUPDATE: GHadeOPFManager.VisitorManager.ExecuteVisitor(
        THadeObject(AObject).getUpdateVisitor, AObject, FConnection);
    posDELETE: GHadeOPFManager.VisitorManager.ExecuteVisitor(
        THadeObject(AObject).getDeleteVisitor, AObject, FConnection);
  end;
end;

procedure ThadeSessionImplementor.saveObjectList(const AObject: THadeVisited);
begin
  if THadeObjectList(AObject).Count = 0 then
    raise EObjectListEmpty.Create(CObjectListEmpty);
  GHadeOPFManager.VisitorManager.ExecuteVisitor(THadeObject(AObject).getSaveListVisitor,
    AObject, FConnection);
end;

procedure ThadeSessionImplementor.save(const AObject: THadeVisited);
begin
  if not (AObject is THadeObjectList) then
    saveObject(AObject)
  else
    saveObjectList(AObject);

  { lets the magic begin :-) ,
    its the easy way to prevent boring start->commit,
    use starttransaction only if you have multiple object to save
    any elegant way will be appreciate }
  if not IsTransactStart then
    commit;
end;

procedure ThadeSessionImplementor.Read(const AObject: THadeVisited;
  const ACriteria: THadeCriteria; const APropsOrder: array of string;
  const AOrderType: THadeOrderType; const limit: integer; const offset: integer);
begin
  if AObject is THadeObjectList then
    GHadeOPFManager.VisitorManager.ExecuteVisitor(
      THadeObject(AObject).getReadListVisitor, AObject, FConnection,
      ACriteria, APropsOrder, AOrderType, limit, offset)
  else
    GHadeOPFManager.VisitorManager.ExecuteVisitor(
      THadeObject(AObject).getReadVisitor, AObject, FConnection,
      ACriteria, APropsOrder, AOrderType, limit, offset);

  if not IsTransactStart then
    commit;
end;

procedure ThadeSessionImplementor.Read(const AObject: THadeVisited;
  const ACriteria: THadeCriteria; const APropsOrder: array of string;
  const AOrderType: THadeOrderType);
begin
  Read(AObject, ACriteria, APropsOrder, AOrderType, 0, 0);
end;

procedure ThadeSessionImplementor.Read(const AObject: THadeVisited;
  const ACriteria: THadeCriteria; const limit: integer; const offset: integer);
begin
  Read(AObject, ACriteria, [''], oASC, limit, offset);
end;

procedure ThadeSessionImplementor.Read(const AObject: THadeVisited);
begin
  Read(AObject, nil, [''], oAsc, 0, 0);
end;

procedure ThadeSessionImplementor.Read(const AObject: THadeVisited;
  const limit: integer; const offset: integer);
begin
  Read(AObject, nil, [''], oASC, limit, offset);
end;

procedure ThadeSessionImplementor.Read(const AObject: THadeVisited;
  const ACriteria: THadeCriteria);
begin
  Read(AObject, ACriteria, [''], oAsc, 0, 0);
end;

procedure ThadeSessionImplementor.StartTransaction;
begin
  if rolledback then
    Rolledback := False;//set rollback to false first

  FConnection.StartTransaction;

  IsTransactStart := True;
end;

procedure ThadeSessionImplementor.Commit;
begin
  {* if transaction was rollback we exit
     surelly its not needed since the backend is smart enough to determine.
     but its usesfull for debugging and logging purpose. *}
  if RolledBack then
    Exit;

  FConnection.Commit;

  IsTransactStart := False;
  Rolledback := False;
end;

procedure ThadeSessionImplementor.Rollback;
begin
  {* (sanitation)if not transact then exit
     its occur when , let say dumb user do session.rollback
     before startransaction *}
  if not isTransactStart then
    exit;

  FConnection.Rollback;
  RolledBack := True;//set rolledback to True so commit know it was rolled back
  IsTransactStart := False;//set again to false
end;

constructor ThadeSessionImplementor.Create(const AConnectionName: string;
  AReturnConnection: boolean);
begin
  FConnection := GHadeConnectionManager.ConnectionByName(AConnectionName);
  FConnection.Connect;
  IsTransactStart := False;
  rolledBack := False;
  fReturnConnection := AReturnConnection;
end;

destructor ThadeSessionImplementor.Destroy;
begin
  if fReturnConnection then
    GHadeConnectionManager.ReturnConnection(FConnection)
  else
    FreeAndNil(FConnection);
  inherited Destroy;
end;

end.

